#pragma once
#include <string>
#include <mutex>
#include <cassert>
#include <fstream>
#include <algorithm>
#include <jsoncpp/json/json.h>
#include "../comm/log.hpp"
#include "../comm/util.hpp"
#include "../comm/httplib.h"
#include "oj_model.hpp"
// #include"oj_model_mysql.hpp"
#include "oj_view.hpp"

//该模块主要是给oj_server.cc去使用的
namespace ns_control
{
    using namespace ns_log;
    using namespace ns_util;
    using namespace ns_model;
    using namespace ns_view;

    class Machine
    {
    public:
        std::string ip;  //编译服务的IP
        int port;        //编译服务的port
        uint64_t load;   //编译服务的负载
        std::mutex *mtx; // c++的锁禁止拷贝，所以使用指针
    public:
        Machine() : ip(""), port(0), load(0), mtx(nullptr)
        {
        }

        ~Machine() {}

        void IncLoad()
        {
            //因为可能有多个客户请求主机编译，负载load需要加锁
            if (mtx)
                mtx->lock();
            ++load;
            if (mtx)
                mtx->unlock();
        }
        void DecLoad()
        {
            if (mtx)
                mtx->lock();
            --load;
            if (mtx)
                mtx->unlock();
        }
        void ResetLoad()
        {
            if (mtx)
                mtx->lock();
            load = 0;
            if (mtx)
                mtx->unlock();
        }
    };

    const std::string sevrice_machine = "./conf/service_machine.conf";
    //负载均衡模块
    class LoadBalance
    {
    private:
        //下标表示每一台主机的编号
        std::vector<Machine> machines; //提供服务的主机
        //在线主机的编号
        std::vector<int> online;
        //离线主机的编号
        std::vector<int> offonline;
        //保证选择主机来编译代码时候的安全
        std::mutex mtx;

    public:
        LoadBalance()
        {
            assert(LoadConf(sevrice_machine)); 
            //走到这里其实就是online数组有三台主机的ip+prot,至于实际上该主机已经运行，还没有确定
            //这里只是前提假设了三台主机已经运行；
            LOG(INFO) << "加载配置文件: " << sevrice_machine << "中的所有主机进入内存成功..."
                      << "\n";
        }
        ~LoadBalance() {}

        //加载配置文件：service_machine.conf (保存ip和port的)
        bool LoadConf(const std::string &machine_conf)
        {
            std::ifstream in(machine_conf);
            if (!in.is_open())
            {
                LOG(FATAL) << "open " << machine_conf << " failed..." << std::endl;
                return false;
            }
            std::string line;
            while (std::getline(in, line))
            {
                std::vector<std::string> tokens; // 0：ip ;1: port
                StringUtil::StringSplit(line, &tokens, ":");
                if (tokens.size() != 2)
                {
                    LOG(WARNING) << "切分" << line << "失败..." << std::endl;
                    continue;
                }

                Machine machine;
                machine.ip = tokens[0];
                machine.port = std::stoi(tokens[1]);
                machine.load = 0;
                machine.mtx = new std::mutex();
                //每台即将要上线的主机机先插入到online数组中
                online.push_back(machines.size());
                machines.push_back(machine);
            }
            in.close();
            return true;
        }
        //只能选择一台主机
        // id:主机的编号
        // m:主机
        bool SmartChoice(int *id, Machine **m)
        {
            // 1.选择一台主机启动编译服务（重点：更新负载）
            // 2.需要离线某台主机

            mtx.lock();
            //负载均衡算法
            //轮询在线主机+hash算法查找负载最小的主机

            int online_number = online.size(); //在线主机的个数
            if (online_number == 0)
            {
                mtx.unlock();
                LOG(FATAL) << "所有提供编译并运行主机都离线,正在呼叫战友修复...."
                           << "\n";
                return false;
            }
            //到这里一定存在至少一台主机在线
            //轮询获取负载最小的主机

            uint64_t min_load = machines[online[0]].load;
            *id = online[0];
            *m = &machines[online[0]];

            for (int i = 0; i < online_number; i++)
            {
                if (min_load > machines[i].load)
                {
                    min_load = machines[i].load;

                    *id = online[i];
                    *m = &machines[online[i]];
                }
            }
            mtx.unlock();
            return true;
        }
        void OlineMachine()
        {
            //全部上线
            mtx.lock();
            online.insert(online.end(), offonline.begin(), offonline.end());
            offonline.erase(offonline.begin(), offonline.end());
            mtx.unlock();

            LOG(INFO) << "所有编译运行服务器主机已上线..."
                      << "\n";
        }
        void OfflineMachine(int id) // id是离线主机id
        {
            mtx.lock();
            for (auto it = online.begin(); it != online.end(); it++)
            {
                if (*it == id)
                {
                    machines[id].ResetLoad();
                    online.erase(it);
                    offonline.push_back(id);
                    break;
                }
            }
            mtx.unlock();
        }
        void slowMachine()
        {
            std::cout << "当前在线主机id: ";
            for (auto id : online)
            {
                std::cout << id << " ";
            }
            std::cout << "\n当前离线主机id: ";

            for (auto id : offonline)
            {
                std::cout << id << " ";
            }
            std::cout << "\n";
        }
    };
    class Control
    {
    private:
        Model _model;             //提供后台数据
        View _view;               //提供网页渲染
        LoadBalance load_balance; //负载均衡器
    public:
        Control() {}
        ~Control() {}

        void RecoverMachine()
        {
            load_balance.OlineMachine();
        }
        bool AllQuestions(std::string *html)
        {
            std::vector<Question> all;
            bool flag = true;
            if (_model.GetAllQuestions(&all))
            {
                std::sort(all.begin(), all.end(), [](const Question &q1, const Question &q2)
                          { return std::stoi(q1.number) < std::stoi(q2.number); });
                //拿到所有题目的数据构建网页返回
                _view.AllExpandHtml(all, html);
            }
            else
            {
                *html = "获取题目列表的网页失败...";
                flag = false;
            }
            return flag;
        }
        bool OneQuestion(const std::string &number, std::string *html)
        {
            Question q;
            bool flag = true;

            if (_model.GetOneQuestion(number, &q))
            {
                //拿到单个题目的信息（编写代码区域）构建网页并对其渲染 返回渲染后的网页html
                _view.OneExpandHtml(q, html);
            }
            else
            {
                *html = "该题目不存在...";
                flag = false;
            }
            return flag;
        }
        //判断题目编写代码是否正确的功能
        void Judge(const std::string &number, const std::string &in_json, std::string *out_json)
        {
            // 0. 根据题号获得题目
            Question q;
            _model.GetOneQuestion(number, &q);

            // 1. 对in_json进行反序列化，获得用户提交的题目编号，题目源代码，input数据
            Json::Reader reader;
            Json::Value in_value;
            reader.parse(in_json, in_value);

            // 2. 重新拼接 用户提交代码和 后端的测试用例代码 构成新的可编译测试的代码
            Json::Value complie_value;
            complie_value["input"] = in_value["input"].asString();
            complie_value["code"] = in_value["code"].asString() + "\n" + q.tail; //用户代码加后端准备的测试用例
            complie_value["cpu_limit"] = q.cpu_limit;
            complie_value["memory_limit"] = q.memory_limit;

            Json::FastWriter writer;
            std::string complie_string = writer.write(complie_value);

            // 3. 选择负载最低主机(要做差错处理)
            //规则：一直选择。直到主机可用，否则就是编译主机服务器全部挂

            while (true)
            {
                int id;
                Machine *m = nullptr;
                if (!load_balance.SmartChoice(&id, &m))
                {
                    break;
                }
                m->IncLoad();
                
                LOG(INFO) << "选择到了最小负载的主机id: " << id << "负载值: " << m->load << " ip: " << m->ip << ":" << m->port << "\n";

                // 4 .发起http请求得到结果
                httplib::Client cli(m->ip, m->port);
                if (auto resp = cli.Post("/compile_and_run", complie_string, "application/json;charset=utf-8"))
                {
                    //请求成功,得到响应结果返回到out_json
                    if (resp->status == 200)
                    {
                        *out_json = resp->body;
                        m->DecLoad();
                        LOG(INFO) << "当前请求主机id:" << id << " ip:" << m->ip << ":" << m->port <<"编译服务成功..."<< std::endl;
                        break;
                    }
                    //请求失败，也让负载--
                    m->DecLoad();
                }
                else
                {
                    //请求失败
                    LOG(ERROR) << "当前请求主机id:" << id << " ip:" << m->ip << ":" << m->port << "请求失败..."<< "\n";                               
                    load_balance.OfflineMachine(id);
                    load_balance.slowMachine(); //调试用的
                }
            }
        }
    };
}
